package ar.edu.itba.tp1.tpmariano;

import java.util.Arrays;

import ar.edu.itba.tp1.gps.api.GPSState;

public class Grid implements GPSState {

	/*
	 * A Tile is composed of one char (2 bytes). Each nibble denotes a color, up to 16 colors
	 * 
	 * UP RIGHT | DOWN LEFT (clockwise)
	 * 
	 * TO ACCESS data: *******************
	 * 
	 * char tile = tiles[y * LENGTH + x];
	 * 
	 * Then to get each component:
	 * 
	 * byte up = (byte) ((tile >> 12) & 0xF);
	 * 
	 * byte right = (byte) ((tile >> 8) & 0xF);
	 * 
	 * byte down = (byte) ((tile >> 4) & 0xF);
	 * 
	 * byte left = (byte) (tile & 0xF);
	 * 
	 * ***********************************
	 * 
	 * Now, the tile matrix is composed of a single dimensional char array which is very fast to access.
	 * 
	 * The objective of this structure is to provide VERY fast comparison and VERY fast rotation, while the specific access may
	 * have some overhead.
	 */

	private static final int LENGTH = 4;

	private char tiles[];

	public Grid(Grid other) {
		this.tiles = other.tiles.clone();
	}

	public Grid() {
		this.tiles = new char[(LENGTH * LENGTH)];
	}
	
	public int getLength(){
		return LENGTH;
	}

	public void putTile(int x, int y, int up, int right, int down, int left) {
		tiles[(y * LENGTH + x)] = (char) (up << 12 | right << 8 | down << 4 | left);
	}

	// Simple method to get a tile composed in a short
	public char getTile(int x, int y) {
		return tiles[(y * LENGTH + x)];
	}

	// Yeah this should be fast enough ^^
	public void rotateRight(int x, int y) {
		tiles[(y * LENGTH + x)] = (char) (((tiles[(y * LENGTH + x)] >> 4) & 4095) | ((tiles[(y * LENGTH + x)] & 0xF) << 12));
	}

	public void rotateLeft(int x, int y) {
		tiles[(y * LENGTH + x)] = (char) ((tiles[(y * LENGTH + x)] << 4) | ((tiles[(y * LENGTH + x)] >> 12) & 0xF));
	}

	public void swap(int srcX, int srcY, int dstX, int dstY) {
		char tmp = tiles[(srcY * LENGTH + srcX)];
		tiles[(srcY * LENGTH + srcX)] = tiles[(dstY * LENGTH + dstX)];
		tiles[(dstY * LENGTH + dstX)] = tmp;
	}

	public String toString() {
		String out = "";
		for (int y = 3; y >= 0; y--) {
			for (int x = 0; x < 4; x++) {

				char tile = tiles[y * LENGTH + x];

				byte up = (byte) ((tile >> 12) & 0xF);
				byte right = (byte) ((tile >> 8) & 0xF);
				byte down = (byte) ((tile >> 4) & 0xF);
				byte left = (byte) (tile & 0xF);

				out += "(" + up + ", " + right + ", " + down + ", " + left + (x == 3 ? ")" : "), ");
			}
			out += "\n";
		}
		return out;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + Arrays.hashCode(tiles);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Grid other = (Grid) obj;
		if (!Arrays.equals(tiles, other.tiles))
			return false;
		return true;
	}

	/*
	 * 
	 * While there may be cases where the same object is checked, each state will be a different reference, so there's no use to
	 * check something we already know (that this!=state)
	 */
	@Override
	public boolean compare(GPSState state) {
		return state != null && Arrays.equals(tiles, ((Grid) state).tiles);
	}

	/*
	 * This method must be mega super ultra fast. While it is still O(n^2), at least we can optimize the neighbor checks.
	 */

	public boolean isGoal() {
		boolean out = true;
		for (int x = 0; x < 4; x++)
			for (int y = 0; y < 4; y++) {
				char tile = tiles[(y * LENGTH + x)];

				// Prevent border overflow by assigning 0 to edges
				char upTile = y < 3 ? tiles[((y + 1) * LENGTH + x)] : 0;
				char rightTile = x < 3 ? tiles[(y * LENGTH + (x + 1))] : 0;
				char downTile = y > 0 ? tiles[((y - 1) * LENGTH + x)] : 0;
				char leftTile = x > 0 ? tiles[(y * LENGTH + (x - 1))] : 0;

				// The algorithm for this check is, for example (UP)
				// tile: ....... | 00 | 01 10 11
				// upTile: 00 01 | 10 | 11
				// ..... result, 0 if equal, 1 if different

				out = out && (((tile >> 12) ^ (upTile >> 4)) & 0xF) == 0; // Check tileUp with up
				out = out && (((tile >> 8) ^ rightTile) & 0xF) == 0;// Check tile with right
				out = out && (((tile >> 4) ^ (downTile >> 12)) & 0xF) == 0; // Check tile with down
				out = out && ((tile ^ (leftTile >> 8)) & 0xF) == 0; // Check tile with left
			}
		return out;
	}

	@Override
	public GPSState clone() {
		return new Grid(this);
	}

	@Override
	public Integer[][] getValues() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean isGoalState() {
		return isGoal();
	}

	@Override
	public GPSState copyState() {
		return clone();
	}

	@Override
	public void addValue(int i, int j, Integer value) {
		// TODO Auto-generated method stub
		
	}
}
